﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;

// A Project is a collection of scripts
namespace delge_squirreleditor
{
    public class Project
    {
        public static int MaxFilenameLength = 20;
        public static string ScriptSubPath = "\\Scripts";
        public static string ScreenSubPath = ScriptSubPath + "\\Screens";
        public static string ClasseSubPath = ScriptSubPath + "\\Classes";
        
        public string Name { get; set; }
        public string Path { get; protected set; }
        public string Filename { get { return Name + ".prj"; } }
        public string Filepath { get { return Path + "\\" + Filename; } }
        public string Shortname { get { return ShortName(Filename); } }

        public List<ProjectItem> Screens { get; protected set; }
        public List<ProjectItem> Classes { get; protected set; }
        public List<ProjectItem> Scripts { get; protected set; }

        public delegate void ProjectItemRenamedEventHandler(object sender, EventArgs e);
        public delegate void ProjectItemRemovedEventHandler(object sender, EventArgs e);

        public event ProjectItemRenamedEventHandler ProjectItemRenamed;
        public event ProjectItemRemovedEventHandler ProjectItemRemoved;

        public Project(string name, string path)
        {
            Name = name;
            Path = path;

            Screens = new List<ProjectItem>();
            Classes = new List<ProjectItem>();
            Scripts = new List<ProjectItem>();
        }

        public ProjectItem AddScreen(string name, bool createFile = false)
        {
            ProjectItem item;
            item = new ProjectItem(name, Path + ScreenSubPath);
            item.Renamed += new ProjectItem.RenamedEventHandler(item_Renamed);
            item.Remove += new ProjectItem.RemoveEventHandler(item_Remove);
            Screens.Add(item);

            if (createFile)
            {
                if (!Directory.Exists(System.IO.Path.GetDirectoryName(item.FilePath)))
                    Directory.CreateDirectory(System.IO.Path.GetDirectoryName(item.FilePath));

                using (FileStream stream = new FileStream(item.FilePath, FileMode.Create))
                {
                    using (TextWriter writer = new StreamWriter(stream))
                    {
                        writer.Write(GenerateScreenScript(SafeName(name)));
                        writer.Close();
                    }
                    stream.Close();
                }

                item.Update();
            }

            return item;
        }

        public ProjectItem AddClass(string name, bool createFile = false)
        {
            ProjectItem item;
            item = new ProjectItem(name, Path + ClasseSubPath);
            item.Renamed += new ProjectItem.RenamedEventHandler(item_Renamed);
            item.Remove += new ProjectItem.RemoveEventHandler(item_Remove);
            Classes.Add(item);

            if (createFile)
            {
                if (!Directory.Exists(System.IO.Path.GetDirectoryName(item.FilePath)))
                    Directory.CreateDirectory(System.IO.Path.GetDirectoryName(item.FilePath));

                using (FileStream stream = new FileStream(item.FilePath, FileMode.Create))
                {
                    using (TextWriter writer = new StreamWriter(stream))
                    {
                        writer.Write(GenerateClassScript(SafeName(name)));
                        writer.Close();
                    }
                    stream.Close();
                }

                item.Update();
            }

            return item;
        }

        public ProjectItem AddGameScript(string name, string initialScreenName, bool createFile = false)
        {
            ProjectItem item;
            item = new ProjectItem("game", Path + ScriptSubPath);
            item.Renamed += new ProjectItem.RenamedEventHandler(item_Renamed);
            item.Remove += new ProjectItem.RemoveEventHandler(item_Remove);
            Scripts.Add(item);

            if (createFile)
            {
                if (!Directory.Exists(System.IO.Path.GetDirectoryName(item.FilePath)))
                    Directory.CreateDirectory(System.IO.Path.GetDirectoryName(item.FilePath));

                using (FileStream stream = new FileStream(item.FilePath, FileMode.Create))
                {
                    using (TextWriter writer = new StreamWriter(stream))
                    {
                        writer.Write(GenerateGameScript(SafeName(name), SafeName(initialScreenName)));
                        writer.Close();
                    }
                    stream.Close();
                }

                item.Update();
            }

            return item;
        }

        public ProjectItem AddScript(string name, bool createFile = false)
        {
            ProjectItem item;
            item = new ProjectItem(name, Path + ScriptSubPath);
            item.Renamed += new ProjectItem.RenamedEventHandler(item_Renamed);
            item.Remove += new ProjectItem.RemoveEventHandler(item_Remove);
            Scripts.Add(item);

            if (createFile)
            {
                if (!Directory.Exists(System.IO.Path.GetDirectoryName(item.FilePath)))
                    Directory.CreateDirectory(System.IO.Path.GetDirectoryName(item.FilePath));

                using (FileStream stream = new FileStream(item.FilePath, FileMode.Create))
                {
                    using (TextWriter writer = new StreamWriter(stream))
                    {
                        writer.Write("");
                        writer.Close();
                    }
                    stream.Close();
                }

                item.Update();
            }

            return item;
        }

        public void RemoveScreen(string name)
        {
            ProjectItem item = null;
            foreach (ProjectItem itemFound in Screens)
            {
                if (itemFound.Name == name)
                {
                    item = itemFound;
                    break;
                }
            }
            Screens.Remove(item);
        }

        public void RemoveClass(string name)
        {
            ProjectItem item = null;
            foreach (ProjectItem itemFound in Classes)
            {
                if (itemFound.Name == name)
                {
                    item = itemFound;
                    break;
                }
            }
            Classes.Remove(item);
        }

        public void RemoveScript(string name)
        {
            ProjectItem item = null;
            foreach (ProjectItem itemFound in Scripts)
            {
                if (itemFound.Name == name)
                {
                    item = itemFound;
                    break;
                }
            }
            Scripts.Remove(item);
        }

        public void PopulateTreeView(TreeView treeView)
        {
            BitmapImage folderIcon = new BitmapImage(new Uri("Images/folder_closed.png", UriKind.Relative));

            EditableTextBlock scriptsHeader = new EditableTextBlock();
            scriptsHeader.Text = "Scripts";
            scriptsHeader.IsEditable = false;
            scriptsHeader.ImageSource = folderIcon;

            EditableTextBlock screensHeader = new EditableTextBlock();
            screensHeader.Text = "Screens";
            screensHeader.IsEditable = false;
            screensHeader.ImageSource = folderIcon;

            EditableTextBlock classesHeader = new EditableTextBlock();
            classesHeader.Text = "Classes";
            classesHeader.IsEditable = false;
            classesHeader.ImageSource = folderIcon;

            TreeViewItem root = new TreeViewItem() { Header = Name };
            TreeViewItem scriptsRoot = new TreeViewItem() { Header = scriptsHeader };
            TreeViewItem screensRoot = new TreeViewItem() { Header = screensHeader };
            TreeViewItem classesRoot = new TreeViewItem() { Header = classesHeader };

            foreach (ProjectItem item in Scripts)
            {
                if (item.Parent != null)
                    (item.Parent as TreeViewItem).Items.Remove(item);
                scriptsRoot.Items.Add(item);
            }
            scriptsRoot.IsExpanded = true;

            foreach (ProjectItem item in Screens)
            {
                if (item.Parent != null)
                    (item.Parent as TreeViewItem).Items.Remove(item);
                screensRoot.Items.Add(item);
            }
            screensRoot.IsExpanded = true;

            foreach (ProjectItem item in Classes)
            {
                if (item.Parent != null)
                    (item.Parent as TreeViewItem).Items.Remove(item);
                classesRoot.Items.Add(item);
            }
            classesRoot.IsExpanded = true;

            scriptsRoot.Items.Add(screensRoot);
            scriptsRoot.Items.Add(classesRoot);
            scriptsRoot.IsExpanded = true;

            root.Items.Add(scriptsRoot);
            root.IsExpanded = true;

            treeView.Items.Clear();
            treeView.Items.Add(root);
        }

        public void Save()
        {
            SaveAs(Filepath);
        }

        public void SaveAs(string filepath = "")
        {
            this.Path = System.IO.Path.GetDirectoryName(filepath);
            this.Name = System.IO.Path.GetFileNameWithoutExtension(filepath);

            using (FileStream stream = new FileStream(filepath, FileMode.Create))
            {
                using (BinaryWriter writer = new BinaryWriter(stream))
                {
                    writer.Write("delge squirrel script project file");     // header
                    writer.Write(0); writer.Write(1);                       // major, minor
                    writer.Write(Screens.Count);                            // screen count
                    foreach (ProjectItem item in Screens)
                        writer.Write(item.Name);                            // name of each screen
                    writer.Write(Classes.Count);                            // classes count
                    foreach (ProjectItem item in Classes)
                        writer.Write(item.Name);                            // name of each class
                    writer.Write(Scripts.Count);                            // script count
                    foreach (ProjectItem item in Scripts)
                        writer.Write(item.Name);                            // name of script class
                    writer.Close();
                }
                stream.Close();
            }

            foreach (ProjectItem item in Scripts)
                if (item.Editor != null) item.Editor.Save();
            foreach (ProjectItem item in Classes)
                if (item.Editor != null) item.Editor.Save();
            foreach (ProjectItem item in Screens)
                if (item.Editor != null) item.Editor.Save();
        }

        public static Project OpenProject(string filepath)
        {
            if (File.Exists(filepath))
            {
                Project project = new Project(System.IO.Path.GetFileNameWithoutExtension(filepath), System.IO.Path.GetDirectoryName(filepath));

                string header;
                int major, minor;
                int screenCount;
                string[] screenNames;
                int classCount;
                string[] classNames;
                int scriptCount;
                string[] scriptNames;
                using (FileStream stream = new FileStream(filepath, FileMode.Open))
                {
                    using (BinaryReader reader = new BinaryReader(stream))
                    {
                        header = reader.ReadString();
                        major = reader.ReadInt32();
                        minor = reader.ReadInt32();
                        
                        screenCount = reader.ReadInt32();
                        screenNames = new string[screenCount];
                        for (int i = 0; i < screenCount; i++)
                            screenNames[i] = reader.ReadString();

                        classCount = reader.ReadInt32();
                        classNames = new string[classCount];
                        for (int i = 0; i < classCount; i++)
                            classNames[i] = reader.ReadString();

                        scriptCount = reader.ReadInt32();
                        scriptNames = new string[scriptCount];
                        for (int i = 0; i < scriptCount; i++)
                            scriptNames[i] = reader.ReadString();

                        reader.Close();
                    }
                    stream.Close();
                }

                foreach (string screenName in screenNames)
                    project.AddScreen(screenName);

                foreach (string className in classNames)
                    project.AddClass(className);

                foreach (string scriptName in scriptNames)
                    project.AddScript(scriptName);

                return project;
            }

            return null;
        }

        public bool Close()
        {
            foreach (ProjectItem item in Screens)
                if ((item.Editor != null) && (item.Editor.Close() == false)) return false;
            foreach (ProjectItem item in Classes)
                if ((item.Editor != null) && (item.Editor.Close() == false)) return false;
            foreach (ProjectItem item in Scripts)
                if ((item.Editor != null) && (item.Editor.Close() == false)) return false;
            Save();
            return true;
        }

        protected void item_Renamed(object sender, EventArgs e)
        {
            OnProjectItemRenamed(EventArgs.Empty);
        }

        protected void item_Remove(object sender, EventArgs e)
        {
            ProjectItem item = (sender as ProjectItem);

            if (item.Parent != null)
            {
                switch (((item.Parent as TreeViewItem).Header as EditableTextBlock).Text)
                {
                    case "Scripts":
                        this.Scripts.Remove(item);
                        break;

                    case "Classes":
                        this.Classes.Remove(item);
                        break;

                    case "Screens":
                        this.Screens.Remove(item);
                        break;
                }
            }

            Save();

            OnProjectItemRemoved(EventArgs.Empty);
        }

        protected virtual void OnProjectItemRenamed(EventArgs e)
        {
            if (ProjectItemRenamed != null) ProjectItemRenamed(this, e);
        }

        protected virtual void OnProjectItemRemoved(EventArgs e)
        {
            if (ProjectItemRemoved != null) ProjectItemRemoved(this, e);
        }

        protected static string SafeName(string name)
        {
            string newName = "";

            foreach (char character in name)
            {
                char c = character;
                if (c == ' ' || c == '¬' || c == '`' || c == '`' ||
                    c == '!' || c == '"' || c == '£' || c == '$' ||
                    c == '%' || c == '^' || c == '&' || c == '*' ||
                    c == '(' || c == ')' || c == '-' || c == '=' ||
                    c == '+' || c == '{' || c == '}' || c == '[' ||
                    c == ']' || c == ':' || c == ';' || c == '@' ||
                    c == '\'' || c == '#' || c == '~' || c == '<' ||
                    c == '>' || c == ',' || c == '.' || c == '/' ||
                    c == '?')
                    c = '_';
                newName += c;
            }

            if (!Char.IsLetter(name[0])) newName = "_" + newName;
            return newName;
        }

        public static bool IsVaildFilename(string filename)
        {
            foreach (char c in filename)
            {
                if (c == '<' || c == '>' || c == ':' || c == '"' ||
                    c == '/' || c == '\\' || c == '|' || c == '?' ||
                    c == '*')
                    return false;
            }

            return true;
        }

        public static string SafeFilename(string filename)
        {
            string newName = "";

            foreach (char c in filename)
            {
                if (c == '<' || c == '>' || c == ':' || c == '"' ||
                    c == '/' || c == '\\' || c == '|' || c == '?' ||
                    c == '*')
                    newName += '_';
                else
                    newName += c;
            }

            return newName;
        }

        public static string ShortName(string name)
        {
            if (name.Length > MaxFilenameLength)
                return name.Substring(0, 4) + " ... " + name.Substring(name.Length - 4, 4);

            return name;
        }

        public static string GenerateScreenScript(string screenName)
        {
            return screenName + " <-\n"                 +
                   "{\n"                                +
                   "\tfunction Render()\n"              +
                   "\t{\n"                              +
                   "\t}\n"                              +
                   "\n"                                 +
                   "\tfunction Update(delta)\n"         +
                   "\t{\n"                              +
                   "\t}\n"                              +
                   "\n"                                 +
                   "\tfunction Load()\n"                +
                   "\t{\n"                              +
                   "\t}\n"                              +
                   "\n"                                 +
                   "\tfunction Unload()\n"              +
                   "\t{\n"                              +
                   "\t}\n"                              +
                   "}\n";
        }

        public static string GenerateGameScript(string gameName, string initialScreenName)
        {
            return "function Startup()\n"                                                   +
                   "{\n"                                                                    +
                   "\tCreateGraphicsWindow(\"" + gameName + "\", 800, 600, 32, false);\n"   +
                   "\t\n"                                                                   +
                   "\tAddScreen(\"" + initialScreenName + "\");\n"                          +
                   "\tSetScreen(\"" + initialScreenName + "\");\n"                          +
                   "}\n"                                                                    +
                   "function Shutdown()\n"                                                  +
                   "{\n"                                                                    +
                   "\tDestroyGraphicsWindow();\n"                                           +
                   "}\n"                                                                    +
                   "function Render()\n"                                                    +
                   "{\n"                                                                    +
                   "\tClearScreen();\n"                                                     +
                   "\t\n"                                                                   +
                   "\tRenderCurrentScreen();\n"                                             +
                   "\t\n"                                                                   +
                   "\tFlip();\n"                                                            +
                   "}\n"                                                                    +
                   "function Update(delta)\n"                                               +
                   "{\n"                                                                    +
                   "\tUpdateCurrentScreen(delta);"                                          +
                   "\t\n"                                                                   +
                   "\tif (IsKeyPressed(KeyCode.Escape) && HasFocus())\n"                    +
                   "\t\tQuit();\n"                                                          +
                   "}\n";
        }

        public static string GenerateClassScript(string className)
        {
            return "class " + className + "\n"  +
                   "{\n"                        +
                   "\tconstructor()\n"          +
                   "\t{\n"                      +
                   "\t}\n"                      +
                   "};\n";
        }
    }
}
